# -*- coding: utf-8 -*-
# standard
import os
import sys
from threading import RLock

# third
import orjson

# local
_P_PATH =  os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
from db import *
from models.base_models import *


"""
@Title:   解析 json 文件
@File: json_files.py
@Author: walle 2023年10日17日 17时55分27秒
@Version: 1.0.0
@Desc: 
"""


DB_THREAD_LOCK = RLock()
RAW_JSON_DIR = os.path.join(_P_PATH, "raw_json")
DOWNLOAD_DIR = os.path.join(R_P_PATH, "downloads")  # 默认下载目录
if not os.path.exists(RAW_JSON_DIR):
    os.makedirs(RAW_JSON_DIR)
if not os.path.exists(DOWNLOAD_DIR):
    os.makedirs(DOWNLOAD_DIR)


class RawJsonHandler:
    """
    处理原始json数据
    """
    _record_map: dict = None

    @classmethod
    def save_json_parse_result_to_db(cls, file_path: str, data: dict) -> list:
        """
        保存json文件的解析结果到数据库, 返回保存的记录数
        :param file_path: _description_
        :param data: _description_
        :return: _description_
        """
        file_name = os.path.basename(file_path)
        DB_THREAD_LOCK.acquire()
        db_session = new_db_session()
        logger.debug(f"正在查询 {file_name} 文件的历史解析记录，请稍候...")
        # 先检查是否有旧的解析json文件的记录
        old_parse_file_record = db_session.query(RawJsonParseRecordOrm).where(RawJsonParseRecordOrm.file_name == file_name).one_or_none()
        if old_parse_file_record is None:
            old_parse_file_record = RawJsonParseRecordOrm(file_name=file_name, file_path=file_path)
            db_session.add(old_parse_file_record)
        db_session.commit()
        json_id = old_parse_file_record.id
        db_session.close()
        DB_THREAD_LOCK.release()
        logger.debug(f"正在分析 {file_name} 文件的内容，请稍候...")
        # todo: 方法1 临时放弃使用 json_id 过滤的那种，如果json文件能保证内部记录不重复或者方法2的性能无法满足需求，那再启用本方法
        # inserted_video_keys = db_session.query(RawVideoObjectOrm.key).where(RawVideoObjectOrm.json_id == json_id).all()
        # todo: 方法2 为了避免不同文件中有相同key的视频，采用了查询全体记录的方式。如果这种方式满足不了性能要求，那就改成使用上面 json_id过滤的那种
        inserted_video_keys = [x[0] for x in db_session.query(RawVideoObjectOrm.key).all()]
        objs = []
        DB_THREAD_LOCK.acquire()
        db_session = new_db_session()
        for key, video_dict in data.items():
            if key in inserted_video_keys:
                continue
            orm_obj = RawVideoObjectOrm(**video_dict, key=key, json_id=json_id)
            db_session.add(orm_obj)
            db_session.flush()
            objs.append({"id": orm_obj.id, "key": key, "url": orm_obj.url, "is_downloaded": 0})
        old_parse_file_record.end_time = datetime.now()
        db_session.commit()
        db_session.close()
        DB_THREAD_LOCK.release()
        return objs


    @classmethod
    def _parse_json(cls, json_path: str) -> List[dict]:
        """
        解析json_path
        :param json_path: _description_
        :return: _description_
        """
        logger.debug(f"正在遍历原始json目录： file = {json_path}")
        data = None
        if not json_path.lower().endswith(".json"):
            logger.debug(f"非json文件，忽略 {json_path}")
            data = []
            return data
        with open(json_path, mode='r', encoding="utf-8") as f:
            try:
                data = orjson.loads(f.read())
            except Exception as e:
                logger.error(e)
                logger.error(f"文件{json_path}解析失败")
        if data is None:
            objs = []
        else:
            objs = cls.save_json_parse_result_to_db(file_path=json_path, data=data)
        num = len(objs)
        logger.debug(f"从{json_path}提取了{num}个视频数据写入数据库")
        return objs

    @classmethod
    def read_json_data(cls, all_path: list = [], dir_path: str = None) -> List[str]:
        """
        读取原始json文件的目录，逐个解析json文件
        :param dir_path: 
        """
        new_downloads = []
        if len(all_path) == 0:
            all_path = cls._get_all_json_files(dir_path)
        for current_path in all_path:
            new_downloads.extend(cls._parse_json(current_path))
        return new_downloads

    @classmethod
    def _get_all_json_files(cls, dir_path: str = None) -> List[str]:
        """
        读取原始json文件的目录，返回所有json文件的绝对路径
        :param dir_path: 
        """
        dir_path = RAW_JSON_DIR if dir_path is None else dir_path
        all_path = []
        if cls._record_map is None:
            cls._record_map = RawJsonParseRecordOrm.get_all_record_map()
        for name in os.listdir(dir_path):
            if name in cls._record_map and cls._record_map[name] == 1:  # 完全解析过的json文件，可以忽略
                continue
            current_path = os.path.join(dir_path, name)
            if os.path.isdir(current_path):
                all_path.extend(cls._get_all_json_files(current_path))
            elif os.path.isfile(current_path) and current_path.endswith(".json"):
                all_path.append(current_path)
            else:
                pass
        return all_path
    

class RawJsonParseRecordOrm(Base, OrmTools):
    """
    原始json文件解析记录
    """

    __tablename__ = "raw_json_parse_record"
    id = Column(Integer, primary_key=True, autoincrement=True)
    file_name = Column(String(255), index=True, nullable=False, doc="文件的名字，同名的文件不做解析")
    file_path = Column(String(255), nullable=False, doc="文件的保存路径")
    is_completed = Column(Integer, default=0, doc="是否解析完毕")
    begin_time = Column(DateTime, default=datetime.now, doc="开始解析的时间")
    end_time = Column(DateTime, default=None, nullable=True, doc="结束解析的时间")
    # 关联
    videos = relationship("RawVideoObjectOrm", sync_backref="json_file", backref="json_file")

    @classmethod
    def get_all_record_map(cls) -> dict:
        """
        返回所有的json文件解析记录的map，包括解析完成的和解析一半的。
        """
        db_session = new_db_session()
        query = db_session.query(cls.file_name, cls.is_completed).all()
        map = {x[0]: x[1] for x in query}
        db_session.close()
        return map


class DownloadStatus(Enum):
    """
    下载状态
    """
    NORMAL: int = 0  #  普通，等待下载
    COMPLETE: int = 1  # 下载完成
    NOTFOUND: int = -1  # 视频没找到，解析失败，一般是视频失效或者私有视频导致的
    REJECT: int = -2  # 下载时被强行断开， 可以重新下载


class RawVideoObjectOrm(Base, OrmTools):
    """
    原始视频json对象，是从原始的json文件中解析出来的结果
    """

    __tablename__ = "raw_video_object"
    id = Column(Integer, primary_key=True, autoincrement=True)
    key = Column(String(255), index=True, unique=True, nullable=False, doc="视频的key")
    url = Column(String(255), index=True, nullable=False, doc="视频对应的页面地址")
    clip = Column(JSON, default=dict, doc="视频片段")
    is_downloaded = Column(Integer, nullable=False, default=0, doc="是否已下载？1已下载，-1 出错")
    download_time = Column(DateTime, nullable=True, default=None, doc="下载时间")
    reason = Column(String(1024), nullable=True, default='', doc="错误原因")
    video_info = Column(String(128), nullable=True, default='', doc="视频信息，视频分辨率和文件尺寸组成字典json字符串，用于比较下载下来的视频尺寸")
    # 外键与关联
    json_id = Column(Integer, ForeignKey("raw_json_parse_record.id"), nullable=False, doc="视频记录对象所在的json文件的解析id")
    # json_file = relationship("RawJsonParseRecordOrm", foreign_keys=[json_id], backref="videos")

    @classmethod
    def get_all_videos_from_db(cls, json_file: str = None) -> List[dict]:
        """
        从数据库查询视频的信息字典
        :param all_record: 如果为true，返回的是全部记录，否则返回的是为 is_downloaded == 0 的记录。也就是未下载过的
        :return: _description_
        """
        db_session = new_db_session()
        cond = or_(cls.is_downloaded == 0, cls.is_downloaded == -2)
        sub = db_session.query(RawJsonParseRecordOrm.id).where(RawJsonParseRecordOrm.file_name == json_file).scalar_subquery() if json_file else None
        if sub is not None:
            query = db_session.query(cls.id, cls.key, cls.url, cls.is_downloaded).where(cond).where(cls.json_id == sub)
        else:
            query = db_session.query(cls.id, cls.key, cls.url, cls.is_downloaded).where(cond)
        data = [dict(zip(['id', 'key', 'url', 'is_downloaded'], x)) for x in query]
        db_session.close()
        return data

    @classmethod
    def mark_downloaded(cls, key: str, status: DownloadStatus, reason: str = "", video_info: str = ""):
        """
        标记某个记录为已下载
        :param key: _description_
        :return: _description_
        """
        update = {"is_downloaded": status.value}
        if status == DownloadStatus.COMPLETE or status == DownloadStatus.REJECT:
            update['download_time'] = datetime.now()
        if reason != "":
            update['reason'] = reason
        if video_info != "":
            update['video_info'] = video_info
        DB_THREAD_LOCK.acquire()
        db_session = new_db_session()
        db_session.query(cls).where(cls.key == key).update(update)
        db_session.commit()
        db_session.close()
        DB_THREAD_LOCK.release()

if __name__ == '__main__':
    
    pass